﻿using System;
using System.Linq;
using System.Collections.Generic;

using Microsoft.Xna.Framework;

using Ah.Testudin.Engine.Actors;
using Ah.Testudin.Engine.Graphics.Scenes.SceneNodes;
using Ah.Testudin.Engine.Graphics.Scenes.SceneNodes.Cameras;
using Ah.Testudin.Engine.Events;
using Ah.Testudin.Engine.Events.Components;

namespace Ah.Testudin.Engine.Graphics.Scenes
{
    public class Scene
    {
        #region Private Member Variables

        private readonly ITestudinRenderer _renderer;

        private readonly IEventManager _eventManager;

        private ICameraSceneNode _cameraNode;

        private RootSceneNode _rootNode;

        private SpriteRootSceneNode _spriteRootNode;

        IDictionary<ActorId, ISceneNode> _actorSceneNodeMap;

        #endregion

        #region Properties

        public ITestudinRenderer Renderer
        {
            get
            {
                return _renderer;
            }
        }

        public IEventManager EventManager
        {
            get
            {
                return _eventManager;
            }
        }

        public ICameraSceneNode Camera {
            get
            {
                return _cameraNode;
            }

            set
            {
                if (value == null)
                {
                    throw new ArgumentException("Camera can't be null");
                }

                _cameraNode = value;
            }
        }

        #endregion

        #region Constructors

        public Scene(
            ITestudinRenderer renderer,
            IEventManager eventManager)
            : this(null, renderer, eventManager)
        {
        }

        public Scene(
            ICameraSceneNode cameraNode,
            ITestudinRenderer renderer,
            IEventManager eventManager)
        {
            _cameraNode = cameraNode;

            if (renderer == null)
            {
                throw new ArgumentNullException("renderer");
            }

            _renderer = renderer;

            if (eventManager == null)
            {
                throw new ArgumentNullException("eventManager");
            }

            _eventManager = eventManager;

            _eventManager.RegisterEventHandler(
                EventType.RenderComponentCreated,
                HandleRenderComponentCreated);

            _eventManager.RegisterEventHandler(
                EventType.ActorMoved,
                HandleActorMovedEvent);

            _eventManager.RegisterEventHandler(
                EventType.ActorDestroyed,
                HandleActorDestroyedEvent);
        }

        #endregion

        #region Methods

        public void Initialize()
        {
            _rootNode = new RootSceneNode();

            _spriteRootNode = new SpriteRootSceneNode();

            _rootNode.AddChildNode(_spriteRootNode);

            _actorSceneNodeMap = new Dictionary<ActorId, ISceneNode>();
        }

        public void Update(GameTime gameTime)
        {
            _rootNode.Update(
                gameTime,
                this);
        }

        public void Render()
        {
            _cameraNode.Render(this);

            _rootNode.PreRender(this);

            _rootNode.Render(this);

            _rootNode.RenderChildren(this);

            _rootNode.PostRender(this);
        }

        public ISceneNode FindActorSceneNode(ActorId actorId)
        {
            return _actorSceneNodeMap.Where(
                x => x.Key == actorId).SingleOrDefault().Value;
        }

        public void AddChildNode(
            ActorId actorId,
            ISceneNode childNode)
        {
            _actorSceneNodeMap.Add(
                new KeyValuePair<ActorId, ISceneNode>(
                    actorId,
                    childNode));

            if (childNode is SpriteSceneNode)
            {
                _spriteRootNode.AddChildNode(childNode);
            }
            else
            {
                _rootNode.AddChildNode(childNode);
            }
        }

        public void RemoveChildNode(ActorId actorId)
        {
            ISceneNode childNode = FindActorSceneNode(actorId);

            if (childNode != null)
            {
                _actorSceneNodeMap.Remove(actorId);

                _rootNode.RemoveChildNode(childNode);
            }
        }

        #endregion

        #region Private Methods

        private void HandleRenderComponentCreated(ITestudinEvent testudinEvent)
        {
            RenderComponentCreatedEvent renderComponentCreatedEvent = testudinEvent as RenderComponentCreatedEvent;

            if (renderComponentCreatedEvent == null)
            {
                throw new ArgumentException("Expected testudinEvent to be of type RenderComponentCreatedEvent");
            }

            AddChildNode(
                renderComponentCreatedEvent.ActorId,
                renderComponentCreatedEvent.SceneNode);
        }

        private void HandleActorMovedEvent(ITestudinEvent testudinEvent)
        {
            ActorMovedEvent actorMovedEvent = testudinEvent as ActorMovedEvent;

            if (actorMovedEvent == null)
            {
                throw new Exception("Expected ActorMovedEvent");
            }

            KeyValuePair<ActorId, ISceneNode> keyValuePair = _actorSceneNodeMap.SingleOrDefault(x => x.Key == actorMovedEvent.ActorId);

            if (keyValuePair.Key != null)
            {
                keyValuePair.Value.Properties.ToWorldTransform = actorMovedEvent.NewTransform;
            }
        }

        private void HandleActorDestroyedEvent(ITestudinEvent testudinEvent)
        {
            ActorDestroyedEvent actorDestroyedEvent = testudinEvent as ActorDestroyedEvent;

            if (_actorSceneNodeMap.Keys.Contains(actorDestroyedEvent.ActorId))
            {
                ISceneNode actorSceneNode = _actorSceneNodeMap[actorDestroyedEvent.ActorId];

                _actorSceneNodeMap.Remove(actorDestroyedEvent.ActorId);

                _rootNode.RemoveChildNode(actorSceneNode);
            }
        }

        #endregion
    }
}
